package edu.hawaii.its381b.client;

import java.util.Date;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

import edu.hawaii.its381b.shared.Score;

public class GameEngine extends Composite{
	/*** Final constants ***/
	private static final int startCrabSpeed = 8, crabStatusIsHome = 3, //startCrabSpeed number: distance in pixels the crab is drawn from where it was.
	mapSpeedmodifier = 0,// 0 = Normal map, no change. Used for testing
	gameSpeed = 1000/24, //How many times a second the game updates the screen. E.g. 1000/8 is 8 times a second
	crabCone = 1, crabLeftHanded = 2, crabHidden = 3, crabElegant = 4, crabJewelled = 5,
	noEvent = 0, 	//Event index (made as constants for readability in code
	playerFoundCorrectShell= 1, moveToMapCenter=2,
	playerFoundKelp1 = 6,playerFoundKelp2 = 7,playerFoundKelp3 = 8,playerLostCrab = 9,
	gameStartCheck = 10, gameOver = 11,
	// Start the crab on the canvas center
	canvasWidth = 600, canvasHeight = 600, canvasStartX = canvasWidth/2, canvasStartY = canvasHeight/2,
	crabBoundary = 200, // <- How close in pixels the crab can get to the canvas edges
	controlBarThickness = 50, distanceFromMapEdge = 50, 
	// MAP SETTINGS
	mapWidth = 1500, mapHeight = 1500, startX = mapWidth/2, startY = mapHeight/2, // Start the crab on the map center
	upArrowAreaX = canvasWidth / 2, upArrowAreaY = 0,
	// HEALTH BAR
	drawHealthBarWidth = 200,drawHealthBarHeight = 40, crabMaxHealth = 1000,
	drawHealthBarAtX = canvasWidth - drawHealthBarWidth -50,drawHealthBarAtY = 5,
	the_Enter_Key = 13;

	//LOCATION info
	//Crab location is kept as two sets of X,Y coordinates
	// Canvas X,Y is where the crab is on the screen
	// regular X,Y is where the crab is on the map, which can be bigger than the screen.
	private int mapLocationX,mapLocationY,crabCanvasx,crabCanvasy,
	mapCanvasDrawStartX, mapCanvasDrawStartY, crabSpeed, touchX, touchY,
	crabsThatAreHome, healthWhenShellWasFound, lastCrab,
	currentEvent = gameStartCheck,currentCrab, finalScore;
	
	private long startTimeAsLong;

	//Assign keyboard keycode "m" to mKey, etc..
	final char the_m_Key = 109, the_M_Key = 77,
	the_r_Key = 114, the_R_Key = 82,
	the_s_Key = 15, the_S_Key = 15;

	private Canvas frontCanvas, backCanvas, itemCanvas, playerCanvas, healthCanvas;

	private String arrowPressed, playerInitials;

	//True if the player touches a tablet
	boolean touchdown, reachedX, reachedY;

	private Banner myBanner;
	private StartControl myStartControl;
	private PageBottom myPageBottom;
	private CrabDisplay myCrabDisplay;

	//CREATE NEW ELEMENTS  
	private GameMap myGameMap;
	private Arrows myGameArrows;
	private SpeedArray mySpeedArray;
	private CrabPlayer myCrabPlayer;
	private MapItems myMapItems;
	private HealthBar myHealthBar; 
	private BackgroundMusic myBackgroundMusic;

	//Note, might end up that only gameMidDisplayPanel is used. 
	VerticalPanel mainPanel, crabBoardDisplay, crabInfoDisplay, bottomDisplay;
	HorizontalPanel gameMidDisplayPanel;

	// START MAIN CLASS ***************************************
	public GameEngine() {
		super();
		
		createCanvases();
		
		myBanner = new Banner();
		myStartControl = new StartControl();
		myPageBottom = new PageBottom();
		myCrabDisplay = new CrabDisplay();
		
		myGameMap = new GameMap( canvasWidth,  canvasHeight,
				mapWidth,  mapHeight,
				startX,  startY,
				canvasStartX, canvasStartY,
				controlBarThickness, crabBoundary, backCanvas);
		
		myGameArrows = new Arrows(canvasWidth, canvasHeight,
				controlBarThickness,backCanvas);
		
		mySpeedArray = new SpeedArray(mapSpeedmodifier);
		
		myCrabPlayer = new CrabPlayer(startX,startY,canvasStartX,canvasStartY,
				canvasWidth, canvasHeight,	mapWidth, mapHeight,
				crabBoundary, controlBarThickness, distanceFromMapEdge, startCrabSpeed, playerCanvas, crabMaxHealth);
		
		myMapItems = new MapItems(mySpeedArray.getRows(), mySpeedArray.getColumns(), 
				canvasWidth, canvasHeight,				
				crabBoundary, controlBarThickness, distanceFromMapEdge,itemCanvas);
		
		myHealthBar = new HealthBar(drawHealthBarAtX,drawHealthBarAtY,drawHealthBarWidth,drawHealthBarHeight,
				crabMaxHealth,healthCanvas); 
		
		myBackgroundMusic = new BackgroundMusic (canvasWidth, canvasHeight,
				crabBoundary, backCanvas);
		
		initGameState();
		
		mainPanel = new VerticalPanel();
		//mainPanel.add(new Banner());
		mainPanel.add(gameMidDisplayPanel = new HorizontalPanel()); 
		gameMidDisplayPanel.add(crabBoardDisplay = new VerticalPanel());
		frontCanvas.setVisible(false);
		this.crabBoardDisplay.add(frontCanvas);
		crabBoardDisplay.add(myBanner);
		crabBoardDisplay.add(myStartControl);
		crabBoardDisplay.add(myPageBottom);
		gameMidDisplayPanel.add(crabInfoDisplay = new VerticalPanel());
		mainPanel.add(bottomDisplay = new VerticalPanel()); 

		//this.mainPanel.sinkEvents(Event.TOUCHEVENTS); 
		//this.mainPanel.sinkEvents(Event.ONKEYDOWN);

		//This sub captures the arrow pressed. When the game updates the screen
		// it will move the crab in whatever direction arrowPressed has stored.
		RootPanel rootPanel = RootPanel.get();
		rootPanel.setStyleName("mainRootStyle");
		rootPanel.addDomHandler(new KeyDownHandler() {

			@Override
			public void onKeyDown(KeyDownEvent event) {
				int inputCode = event.getNativeKeyCode();

				if (inputCode == the_Enter_Key && myStartControl.gameHasNotStarted()){
					playerInitials = myBanner.getInitials();
					myStartControl.updateGameStart();
					// RootPanel.get().add(new Label("Hello: " +playerInitials +". Can you get over 9000 points?" ));
				}

				if (event.isLeftArrow()) {
					arrowPressed = "Left";
					touchdown = true;
				}
				if (event.isRightArrow()) {
					arrowPressed = "Right";
					touchdown = true;
				}
				if (event.isUpArrow()) {
					arrowPressed = "Up";
					touchdown = true;
				}
				if (event.isDownArrow()) {
					arrowPressed = "Down";
					touchdown = true;
				}
				int key = event.getNativeKeyCode();
				// GWT.log("Key code: " +  key + ", " + (char)key);
				// This detects if the "M" key is pressed.
				if (key == the_M_Key ) { // Toggle music
					myBackgroundMusic.hitButton();
				}
				if(myMapItems.gameOverAnimationIsComplete()) {
					if(key == the_R_Key) { //restart the Game
						showScoreboard(); //restartGame();
					}
				}
			}
		}, KeyDownEvent.getType());

		//This sub captures the arrow released. When the game updates the screen
		// it will know not to move the crab.
		RootPanel.get().addDomHandler(new KeyUpHandler() {

			@Override
			public void onKeyUp(KeyUpEvent event) {

				if (event.isLeftArrow()) {
					arrowPressed = "Nothing";
					touchdown = false;
				}
				if (event.isRightArrow()) {
					arrowPressed = "Nothing";
					touchdown = false;
				}
				if (event.isUpArrow()) {
					arrowPressed = "Nothing";
					touchdown = false;
				}
				if (event.isDownArrow()) {
					arrowPressed = "Nothing";
					touchdown = false;
				}
			}
		}, KeyUpEvent.getType());

		initWidget(mainPanel);
		timer.scheduleRepeating(gameSpeed);	
		
		startTimeAsLong = (new Date()).getTime();
	}

	public void makeGameBoard(){
		updateBoardDrawing();
 
		frontCanvas.sinkEvents(Event.TOUCHEVENTS);
		frontCanvas.sinkEvents(Event.ONKEYDOWN);
		
		frontCanvas.addTouchStartHandler(new TouchStartHandler(){

			@Override
			public void onTouchStart(TouchStartEvent event) {
				JsArray<Touch> touchArray = event.getTouches();
				for(int i = 0; i < touchArray.length(); i++) {
					Touch t = touchArray.get(i);
					checkTouch(t.getRelativeX(frontCanvas.getElement()), t.getRelativeY(frontCanvas.getElement()));
				}
				if (currentEvent == gameStartCheck) {
					//The player touched the start screen. Begin the game
					myStartControl.updateGameStart();
					myBackgroundMusic.turnOn();
				}
			}
		});

		frontCanvas.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				checkTouch(event.getRelativeX(frontCanvas.getElement()), event.getRelativeY(frontCanvas.getElement()));
			}
		});
	}

	private void createCanvases(){
		frontCanvas = Canvas.createIfSupported();
		backCanvas = Canvas.createIfSupported();
		itemCanvas = Canvas.createIfSupported();
		playerCanvas = Canvas.createIfSupported();
		healthCanvas = Canvas.createIfSupported();
		
		if (frontCanvas == null) {
			// this.mainPanel.add(new Label("No canvas, get Chrome!"));
			this.mainPanel.add(new Label("No canvas, get Chrome!"));
			return;
		}
		setCanvasDimensions(frontCanvas);
		setCanvasDimensions(backCanvas);
		setCanvasDimensions(itemCanvas);
		setCanvasDimensions(healthCanvas);
		setCanvasDimensions(playerCanvas);
		
	}

	private void setCanvasDimensions(Canvas currentCanvas){
		currentCanvas.setWidth(canvasWidth+"px");
		currentCanvas.setHeight(canvasHeight+"px");
		currentCanvas.setCoordinateSpaceWidth(canvasWidth);
		currentCanvas.setCoordinateSpaceHeight(canvasHeight);
	}

	private void initGameState() {
		crabsThatAreHome = 0;
		healthWhenShellWasFound = 0;
		currentCrab = crabCone;
		arrowPressed = "Nothing";
		reachedX = false;
		reachedY = false;
	}

	private void restartGame(){
		initGameState();
		myMapItems.initializeMapItems(mySpeedArray.getRows(), mySpeedArray.getColumns(), 
				canvasWidth, canvasHeight,
				crabBoundary, controlBarThickness, distanceFromMapEdge,itemCanvas);

		myCrabPlayer.initializeCrabPlayer(startX,startY,canvasStartX,canvasStartY,
				canvasWidth, canvasHeight, mapWidth, mapHeight,
				crabBoundary, controlBarThickness, distanceFromMapEdge, startCrabSpeed,
				playerCanvas, crabMaxHealth);
		myGameMap.setmapCanvasDrawUpperLeftXY(canvasWidth/2 - mapWidth/2,
				canvasHeight/2 - mapHeight/2);
		myCrabDisplay.updateCrabInfoDisplayWindow(crabCone);
		myCrabDisplay.setScore(0);


		currentEvent = noEvent;
	}

	final Timer timer = new Timer() {
		@Override
		public void run() {
			if(gameIsOver()){
				currentEvent = gameOver;
			}else{
				if(myCrabPlayer.getCrabHealth(currentCrab)<2){
					currentEvent = playerLostCrab;
					myMapItems.setRemoveCrabNow();
				}
			}
			if(currentEvent == noEvent){
				myCrabPlayer.reduceCrabHealth(currentCrab, 1);

				updateMovement();		    
				updateBoardDrawing();
				checkCollision(myCrabPlayer.getGridLocationX(),myCrabPlayer.getGridLocationY());
			}else{
				//clear the player crab image to instead show animation of crab entering shell 
				runEvents();
				if(currentEvent == noEvent){
					//collision was cleared, do not run board-clearing update
				}else{
					updateBoardDrawing();
					//Don't make a walking sound during an event.
					myCrabPlayer.walkFXTurnOff();
				}
			}
		}
	};


	private void checkCollision(int xPlayerGridLocation, int yPlayerGridLocation ) {
		//change eventDetected to a value > 0 (ID of the Event) if player is on an item it can can interact with
		int itemCount = myMapItems.getItemCount(), xItemGridLocation, yItemGridLocation;

		for(int itemIDNumber = 1; itemIDNumber <= itemCount; itemIDNumber++){
			xItemGridLocation = myMapItems.getItemXGridLocation(itemIDNumber);
			yItemGridLocation = myMapItems.getItemYGridLocation(itemIDNumber);
			if (xPlayerGridLocation == xItemGridLocation &&		// If the X and Y of the item match the X and Y location of the player,
					yPlayerGridLocation == yItemGridLocation	){ 	//	there is a collision, and an event may take place

				currentEvent = itemIDNumber; //Events are numbered. The ID of the item is used as the ID of the event
				if(itemIDNumber <= crabJewelled){ //If the collision is a shell, only the shell with
					if(itemIDNumber == myCrabPlayer.getCurrentCrab()){ //  the same ID as the crab (1 to 1, 2 to 2) is correct
						currentEvent = playerFoundCorrectShell; //On a match, the event is 1
					}else{
						currentEvent = noEvent; //No match (for shells only) should produce no event
					}
				}
			}
		}
	}

	private void runEvents(){
		switch (currentEvent) {
		case playerFoundCorrectShell: 
			if (this.myMapItems.crabCanGoHome(currentCrab)){
				//Do nothing. The animation of the crab ent			//Do nothing. The animation of the crab ent			//Do nothing. The animation of the crab entering a cone shell is running
				healthWhenShellWasFound = myCrabPlayer.getCrabHealth(currentCrab);
			}else{
				//Crab has gone home. Get next crab if possible
				if(myCrabPlayer.nextCrabCanBePlayed()){
					myCrabDisplay.addToScore(healthWhenShellWasFound);
					currentCrab = myCrabPlayer.getCurrentCrab();
					myMapItems.clearCrabOnBar(currentCrab);//clear image of next crab up from black control bar
					myCrabDisplay.updateCrabInfoDisplayWindow(currentCrab);
					currentEvent = moveToMapCenter;
				}else{
					myCrabDisplay.addToScore(healthWhenShellWasFound);
					currentEvent = gameOver;
				}
			}
			break;

		case playerFoundKelp1:
			if(myMapItems.thisKelpIsGone(playerFoundKelp1)){
				currentEvent = noEvent;
			}else{
				if(myMapItems.crabAteKelpLeaf()){
					myCrabPlayer.increaseCrabHealth(myCrabPlayer.getCurrentCrab(), 250);
					myMapItems.clearCrabAteAKelp();
				}
			}
			break;

		case playerFoundKelp2:
			if(myMapItems.thisKelpIsGone(playerFoundKelp2)){
				currentEvent = noEvent;
			}else{
				if(myMapItems.crabAteKelpLeaf()){
					myCrabPlayer.increaseCrabHealth(myCrabPlayer.getCurrentCrab(), 250);
					myMapItems.clearCrabAteAKelp();
				}
			}
			break;

		case playerFoundKelp3:
			if(myMapItems.thisKelpIsGone(playerFoundKelp3)){
				currentEvent = noEvent;
			}else{
				if(myMapItems.crabAteKelpLeaf()){
					myCrabPlayer.increaseCrabHealth(myCrabPlayer.getCurrentCrab(), 250);
					myMapItems.clearCrabAteAKelp();
				}
			}
			break;

		case moveToMapCenter:
			if(startX> myCrabPlayer.getLocationX() && reachedX == false){
				myCrabPlayer.moveRight(6);
				if(startX<= myCrabPlayer.getLocationX()){
					reachedX = true;
				}
			}else{
				myCrabPlayer.moveLeft(6);
				if(startX>= myCrabPlayer.getLocationX()){
					reachedX = true;
				}
			}
			if(startY> myCrabPlayer.getLocationY()){
				myCrabPlayer.moveDown(6);
				if(startY<= myCrabPlayer.getLocationY()){
					reachedY = true;
				}
			}else{
				myCrabPlayer.moveUp(6);
				if(startY>= myCrabPlayer.getLocationY()){
					reachedY = true;
				}
			}

			if(reachedX == true && reachedY == true){
				currentEvent = noEvent;
				reachedX = false;
				reachedY = false;
			}
			break;

		case playerLostCrab:
			myMapItems.setCurrentCrab(currentCrab);
			if(myMapItems.crabIsBeingRemoved() ){
				//Do nothing. Crab removal animation is running.
			}else{
				if(myCrabPlayer.nextCrabCanBePlayed()){
					currentCrab = myCrabPlayer.getCurrentCrab();
					myMapItems.clearCrabOnBar(currentCrab);//clear image of next crab up from black control bar
					this.myCrabPlayer.setCrabHealth(currentCrab, 1000);
					myCrabDisplay.updateCrabInfoDisplayWindow(currentCrab);
					currentEvent = moveToMapCenter;
				}else{
					currentEvent = gameOver;
				}
			}
			break;

		case gameStartCheck:
			if(myStartControl.gameHasNotStarted() ){
				myBanner.setFocus();
				//Do nothing else. We are on the start screen. Enter, or start button has not been pushed.
			}else{
				//Start had been pushed. Remove greeting panels,
				// put up game board and game info panels. 
				crabBoardDisplay.remove(myBanner);
				crabBoardDisplay.remove(myStartControl);	
				crabBoardDisplay.remove(myPageBottom);
				makeGameBoard();
				crabInfoDisplay.add(myCrabDisplay);
				currentEvent = noEvent;
				frontCanvas.setVisible(true);
			}
			break;

		case gameOver:
			finalScore = myCrabDisplay.getScore();
			break;
		}
	}

	// -Send the crab's map location (X,Y) to the speed array
	// -Get back the speed for the part of the terrain it is on.
	private int speedFromMap(){
		return mySpeedArray.getMapSpeed(
				myCrabPlayer.getLocationX(),
				myCrabPlayer.getLocationY());
	}

	public void checkTouch(int x, int y) {
		touchX = x;
		touchY = y;

		// check the bounds of the four arrows on the screen.
		arrowPressed = this.myGameArrows.CheckArrowPressed(touchX, touchY);
		if(arrowPressed == "Nothing"){
			touchdown = false;
		}else{
			touchdown =true;
		}
		myBackgroundMusic.toggleMusicButtonIfPressed(touchX, touchY);
		if((currentEvent == gameOver) && (myMapItems.restartTouched(touchX, touchY))){
			//restartGame();
			showScoreboard();
		}
		//this.mainPanel.add(new Label("x: " + touchX + ", y: " + touchY));
	}

	public void updateMovement(){
		if(touchdown){
			//When a movement direction is captured, 
			//   move the crab its speed based on the type of terrain it is on 
			this.myCrabPlayer.walkFXTurnOn();
			if (arrowPressed == "Up") {
				crabCanvasy = myCrabPlayer.moveUp(speedFromMap());
			}
			if (arrowPressed == "Down") {
				crabCanvasy = myCrabPlayer.moveDown(speedFromMap());
			}
			if (arrowPressed == "Left") {
				crabCanvasx = myCrabPlayer.moveLeft(speedFromMap());
			}
			if (arrowPressed == "Right") {
				crabCanvasx = myCrabPlayer.moveRight(speedFromMap());
			}
		}else{
			this.myCrabPlayer.walkFXTurnOff();
		}
	}

	private boolean gameIsOver(){
		for(int crabToCheck = 1; crabToCheck <= 5;crabToCheck ++){
			if(myMapItems.getCurrentCrabsStatus(crabToCheck) < 2){
				return false;
			}
		}
		return true;
	}

	private void updateBoardDrawing() {
		
		Context2d backContext = backCanvas.getContext2d();
		Context2d itemContext = itemCanvas.getContext2d();
		Context2d healthBarContext = this.healthCanvas.getContext2d();
		Context2d playerContext = playerCanvas.getContext2d();
		Context2d frontContext = frontCanvas.getContext2d();

		//Clear Map Board area, not black border and controls.
		//		backContext.clearRect(crabBoundary/2, crabBoundary/2,
		//							  canvasWidth - crabBoundary, canvasHeight - crabBoundary);

		//Draw Map		Map needs to know the X,Y of the crab on both the map and canvas area.
		this.myGameMap.draw(myCrabPlayer.getLocationX(),myCrabPlayer.getLocationY(),
				myCrabPlayer.getCanvasLocationX(),myCrabPlayer.getCanvasLocationY());

		//All items need to know what area of the map they are on is visible.
		// The mapDraw StartX,Y below keeps that info updated for them
		mapCanvasDrawStartX =  myGameMap.getCanvasUpperLeftDrawStartX();
		mapCanvasDrawStartY =  myGameMap.getCanvasUpperLeftDrawStartY();

		//Draw Items visible on map, and their animations if an event is detected to be occurring.
		this.myMapItems.draw(currentEvent, myCrabPlayer.getLocationX(),myCrabPlayer.getLocationY(),
				myCrabPlayer.getCanvasLocationX(),myCrabPlayer.getCanvasLocationY(),
				mapCanvasDrawStartX, mapCanvasDrawStartY, currentCrab);
		backContext.drawImage(itemContext.getCanvas(), 0, 0);
		//Draw Controls
		myGameArrows.draw();
		myBackgroundMusic.drawButton();
		myHealthBar.setHealth(myCrabPlayer.getCrabHealth(currentCrab));
		backContext.drawImage(healthBarContext.getCanvas(), 0, 0);	

		if(currentEvent == 0){
			//Draw Player -Only draw player if no event (animation) is running
			this.myCrabPlayer.draw(currentCrab);
			backContext.drawImage(playerContext.getCanvas(), 0, 0);
		}

		frontContext.drawImage(backContext.getCanvas(), 0, 0);
	}

	public void showScoreboard() {
		Score score = new Score();
		score.setName(myBanner.getInitials());
		score.setScore(finalScore);
		score.setTime((new Date()).getTime() - startTimeAsLong);
		RootPanel.get().clear();
		RootPanel.get().add(new Scoreboard(score));
	}
}
